package com.example.renderer;

import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.util.Log;

import com.example.bounce.R;
import com.example.glutil.RawResourceReader;
import com.example.state.AppState;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

/**
 * Created by Lucas on 06/06/13.
 */
public class ScreenRenderer implements GLSurfaceView.Renderer {
    /**
     * The debug TAG
     */
    private static final String TAG = "ScreenRenderer";
    /**
     * An array of the projection Matrix
     */
    private float[] mProjMatrix = new float[16];
    /**
     * An array of the View Matrix
     */
    private float[] mVMatrix = new float[16];
    /**
     * The id of the openGL program
     */
    private int mProgram;
    /**
     * The id of the Model-View-Projection Matrix
     */
    private int muMVPMatrixHandle;
    /**
     * The id of the position
     */
    private int maPositionHandle;
    /**
     * The id of the texture
     */
    private int maTextureHandle;
    /**
     * The vertical offset of the view
     */
    public static float YOFFSET;
    /**
     * The current state of the application
     */
    private AppState _appState;
    /**
     * The context of the application
     */
    private Context mContext;

    /**
     * The constructor
     * @param context the context of the application
     */
    public ScreenRenderer(Context context){
        mContext = context;
        Log.i(TAG, "Constructor");
        YOFFSET = 0;
    }

    /**
     * Set the new state of the application
     * @param appState
     */
    public void setAppState(AppState appState){
        _appState = appState;
    }

    /**
     * Draw the list of object of the current state
     * @param glUnused
     */
    public void onDrawFrame(GL10 glUnused) {
        if(!_appState.isInitiate()){
            _appState.setGLHandler(maPositionHandle, maTextureHandle, muMVPMatrixHandle,
                    mVMatrix, mProjMatrix);
        }

        GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
        GLES20.glUseProgram(mProgram);
        checkGlError("glUseProgram", 0);
        _appState.draw();
        GLES20.glFinish();
        GLES20.glFlush();
    }

    /**
     * Call when the surface change (during the application in the context of the application)
     * @param glUnused
     * @param width
     * @param height
     */
    public void onSurfaceChanged(GL10 glUnused, int width, int height) {
        Log.i(TAG, "onSurfaceChanged");

        float ratio = (1.0f*height)/(1.0f*width);
        GLES20.glViewport(0, 0, width, height);
        Matrix.orthoM(mProjMatrix, 0, 0f, 2f, 0f, 2f*ratio, -1, 6);
    }

    /**
     * Change the view of the current state
     * @param delta the delta to change the view
     */
    public void changeView(float delta) {
        YOFFSET = YOFFSET + delta*0.03f;
        if (YOFFSET < 0 ){
            YOFFSET =0;
        }
        Matrix.setLookAtM(mVMatrix, 0, 0f, YOFFSET, -5f, 0f, YOFFSET, 0f, 0f, 1f, 0f);
    }

    /**
     * Called when the surface id created
     * @param glUnused
     * @param config
     */
    public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
        Log.i(TAG, "onSurfaceChange");
        // Ignore the passed-in GL10 interface, and use the GLES20
        // class's static methods instead.
        int vertexShaderHandle = loadVertexShader();
        int fragmentShaderHandle = loadFragmentShader();
        mProgram = this.createProgram(vertexShaderHandle, fragmentShaderHandle);

        GLES20.glEnable(GLES20.GL_BLEND);
        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);

        // Get OpenGL attributes
        maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
        checkGlError("glGetAttribLocation aPosition", maPositionHandle);
        maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
        checkGlError("glGetAttribLocation aTextureCoord", maTextureHandle);
        muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
        checkGlError("glGetUniformLocation uMVPMatrix", muMVPMatrixHandle);
        Matrix.setLookAtM(mVMatrix, 0, 0f, 0f, -5f, 0f, 0f, 0f, 0f, 1f, 0f);
    }

    /**
     * Create the program with the shader
     * @param vertexShaderHandle
     * @param fragmentShaderHandle
     * @return programHandle
     */
    private int createProgram(int vertexShaderHandle, int fragmentShaderHandle){
        Log.i(TAG, "createProgram");
        // Create a program object and store the handle to it.
        int programHandle = GLES20.glCreateProgram();

        if (programHandle != 0)
        {
            // Bind the vertex shader to the program.
            GLES20.glAttachShader(programHandle, vertexShaderHandle);

            // Bind the fragment shader to the program.
            GLES20.glAttachShader(programHandle, fragmentShaderHandle);

            // Bind attributes
            GLES20.glBindAttribLocation(programHandle, 0, "a_Position");
            GLES20.glBindAttribLocation(programHandle, 1, "a_Color");

            // Link the two shaders together into a program.
            GLES20.glLinkProgram(programHandle);

            // Get the link status.
            final int[] linkStatus = new int[1];
            GLES20.glGetProgramiv(programHandle, GLES20.GL_LINK_STATUS, linkStatus, 0);

            // If the link failed, delete the program.
            if (linkStatus[0] == 0)
            {
                GLES20.glDeleteProgram(programHandle);
                programHandle = 0;
            }
        }

        if (programHandle == 0)
        {
            throw new RuntimeException("Error creating program.");
        }
        return programHandle;
    }

    /**
     * Load the fragment Shader
     * @return fragmentShaderHandler
     */
    private int loadFragmentShader(){
        Log.i(TAG, "loadFragmentShader");
        // Load in the fragment shader shader.
        int fragmentShaderHandle = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);

        if (fragmentShaderHandle != 0)
        {
            // Pass in the shader source.
            GLES20.glShaderSource(fragmentShaderHandle, getFragmentShader());

            // Compile the shader.
            GLES20.glCompileShader(fragmentShaderHandle);

            // Get the compilation status.
            final int[] compileStatus = new int[1];
            GLES20.glGetShaderiv(fragmentShaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0);

            // If the compilation failed, delete the shader.
            if (compileStatus[0] == 0)
            {
                GLES20.glDeleteShader(fragmentShaderHandle);
                fragmentShaderHandle = 0;
            }
        }

        if (fragmentShaderHandle == 0)
        {
            throw new RuntimeException("Error creating fragment shader.");
        }
        return fragmentShaderHandle;
    }

    /**
     * Load the vertex Shader
     * @return vertexShaderHandle
     */
    private int loadVertexShader(){
        Log.i(TAG, "loadVertextFragment");
        // Load in the vertex shader.
        int vertexShaderHandle = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);

        if (vertexShaderHandle != 0)
        {
            // Pass in the shader source.
            GLES20.glShaderSource(vertexShaderHandle, getVertexShader());

            // Compile the shader.
            GLES20.glCompileShader(vertexShaderHandle);

            // Get the compilation status.
            final int[] compileStatus = new int[1];
            GLES20.glGetShaderiv(vertexShaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0);

            // If the compilation failed, delete the shader.
            if (compileStatus[0] == 0)
            {
                GLES20.glDeleteShader(vertexShaderHandle);
                vertexShaderHandle = 0;
            }
        }

        if (vertexShaderHandle == 0)
        {
            throw new RuntimeException("Error creating vertex shader.");
        }
        return vertexShaderHandle;
    }


    protected String getVertexShader()
    {
        return RawResourceReader.readTextFileFromRawResource(mContext, R.raw.point_vertex_shader);
    }

    protected String getFragmentShader()
    {
        return RawResourceReader.readTextFileFromRawResource(mContext, R.raw.point_fragment_shader);
    }

    /**
     * Utility method for debugging OpenGL calls. Provide the name of the call
     * just after making it:
     *
     * <pre>
     * mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
     * MyGLRenderer.checkGlError("glGetUniformLocation");</pre>
     *
     * If the operation is not successful, the check throws an error.
     *
     * @param glOperation - Name of the OpenGL call to check.
     */
    public static void checkGlError(String glOperation, int glAttribute) {
        int error;
        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
            Log.e(TAG, glOperation + ": glError " + error);
            throw new RuntimeException(glOperation + ": glError " + error);
        }
    }

}
